#include "script/ScriptDocumentOperation.h"
#include "script/ScriptSemanticTokenLegend.h"

using namespace simodo;

using SemanticTokenModifiers = variable::BitFlags<SemanticTokenModifier>;

/// \file
/// \see https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_semanticTokens

/// \todo Очень непонятно и очень некрасиво. Стоит переписать, чтобы было более понятно.
SemanticTokenType ScriptDocumentOperation::selectSemanticTokenType(const variable::Variable & var, const SemanticTokenType function_index) const {
    if (var.type() == variable::ValueType::Function)
        return function_index;

    const std::vector<std::u16string> & tokenTypes = _doc.server().server_capabilities().semanticTokensProvider.legend.tokenTypes;

    if (const variable::Value & spec_origin_value = var.spec().object()->find(variable::SPEC_ORIGIN); spec_origin_value.isString())
        for (size_t type_index = 0; type_index < tokenTypes.size(); ++type_index)
            if (spec_origin_value.getString() == tokenTypes[type_index])
                return static_cast<SemanticTokenType>(type_index);

    if (!var.spec().object()->variables().empty())
        return SemanticTokenType::BuiltInModule;  // \todo Проверить, тот ли тип?

    switch (var.type()) {
        case variable::ValueType::Object:
            return SemanticTokenType::Struct;  // \todo Проверить, тот ли тип?
        default:
            break;
    }
    return SemanticTokenType::Property;  // \todo Проверить, тот ли тип?
}

SemanticTokenModifiers makeSemanticTokenModifiers(const variable::Variable & var) {
    SemanticTokenModifiers modifiers;

    if (!var.spec().object()->find(variable::SPEC_READONLY).isNull())  modifiers.set(SemanticTokenModifier::Readonly);
    if (!var.spec().object()->find(variable::SPEC_PARAMETER).isNull()) modifiers.set(SemanticTokenModifier::Parameter);
    if (!var.spec().object()->find(variable::SPEC_INPUT).isNull())     modifiers.set(SemanticTokenModifier::Input);
    if (!var.spec().object()->find(variable::SPEC_OUTPUT).isNull())    modifiers.set(SemanticTokenModifier::Output);

    return modifiers;
}

struct TokenInfo { int64_t length, type, modifiers; };
struct PositionComp {
    bool operator() (const lsp::Position & x1, const lsp::Position & x2) const  { return x1 < x2; }
};

variable::Value ScriptDocumentOperation::produceSemanticTokensResponse() const
{
    std::map<lsp::Position, TokenInfo, PositionComp> tokens;
    const std::function insertToken =
            [&tokens](const inout::TokenLocation & loc, const SemanticTokenType type, const SemanticTokenModifiers & modifiers) {
                const auto & range = loc.range();

                assert(range.start().line() == range.end().line());  // Предполагаем, что токен располагается на одной строке!
                const TokenInfo & tokenInfo = {
                        .length    = range.end().character() - range.start().character(),
                        .type      = static_cast<int64_t>(type),
                        .modifiers = modifiers.getRawValue(),
                };

                tokens.insert({ loc.range().start(), tokenInfo });
            };

    for(const variable::Variable & v : _semantic_data.declared())
        if (isInCurrentFile(v.location())) {
            insertToken(v.location(),
                        selectSemanticTokenType(v, SemanticTokenType::Function),             // \todo Проверить, тот ли тип?
                        makeSemanticTokenModifiers(v) | SemanticTokenModifier::Declaration); // \todo Проверить, тот ли модификатор?
        }

    for(const auto & [declared_variable, using_location] : _semantic_data.used())
        if (isInCurrentFile(using_location)) {
            auto modifiers = makeSemanticTokenModifiers(declared_variable) | SemanticTokenModifier::Anchor;

            if (isRemoteFunctionLaunch(using_location))
                modifiers.set(SemanticTokenModifier::Remote);

            insertToken(using_location,
                        selectSemanticTokenType(declared_variable, SemanticTokenType::Method), // \todo Проверить, тот ли тип?
                        modifiers);                                                            // \todo Проверить, тот ли модификатор?
        }

    for(const auto & [t, ref] : _semantic_data.refs())
        if (isInCurrentFile(t.location())) {
            insertToken(t.location(),
                        SemanticTokenType::String,       // \todo Проверить, тот ли тип?
                        SemanticTokenModifier::Anchor);  // \todo Проверить, тот ли модификатор?
        }

    for(const inout::Token & t : _semantic_data.tokens())
        if (isInCurrentFile(t.location()) and t.qualification() == inout::TokenQualification::Keyword) {
            SemanticTokenType type;
            if (t.lexeme() == u"true" || t.lexeme() == u"false")
                type = SemanticTokenType::Boolean;  // \todo Проверить, тот ли тип?
            else if (t.lexeme() == u"undef" || t.lexeme() == u"null")
                type = SemanticTokenType::Number;   // \todo Проверить, тот ли тип?
            else
                type = SemanticTokenType::Keyword;  // \todo Проверить, тот ли тип?

            insertToken(t.location(), type, SemanticTokenModifiers::Empty);
        }

    std::vector<variable::Value> sem_tokens;

    for(const auto & [p, t] : tokens) {
        sem_tokens.emplace_back(static_cast<int64_t>(p.line()));
        sem_tokens.emplace_back(static_cast<int64_t>(p.character()));
        sem_tokens.emplace_back(t.length);
        sem_tokens.emplace_back(t.type);
        sem_tokens.emplace_back(t.modifiers);
    }

    return variable::Object{{{ u"data", sem_tokens }}};
}
